编写 ls -l: stat

ls -l 列出一系列的文件信息

$ man -k file | grep -i status

找到 stat

用 stat 得到文件信息

磁盘上的文件有很多属性, 比如大小, 所有者ID. 如果需要得到文件属性, 进程可以定义一个结构 struct stat, 然后调用 stat, 告诉内核把文件属性存放到这个结构中

  Stat
目标 得到文件的属性
头文件 #include <sys/stat.h>
原型 int result = stat(char *fname, struct stat *bufp)
参数 fname 文件名, bufp 指向buffer的指针
返回值 -1 error, 0 success

下面代码展示了如何用 stat 来得到文件的大小

#include <stdio.h>
#include <sys/stat.h>

int main()
{
    struct stat infobuf;

    if (stat("/etc/passwd", &infobuf) == -1)
        perror("/etc/passwd");
    else
        printf("The size of /etc/passwd is %ld\n", infobuf.st_size);
    return 0;
}

stat 把文件的信息复制到结构 infobuf 中, 程序从成员变量 st_size 中读到文件大小.

stat 提供的其他信息

struct stat {
    dev_t       st_dev;         /* ID of device containing file */
    ino_t       st_ino;         /* I-node 节点 */
    mode_t      st_mode;        /* 文件类型和许可权限 */
    nlink_t     st_nlink;       /* 硬链接数目 */
    off_t       st_size;        /* 文件字节数 */
    uid_t       st_uid;         /* 文件所有者id */
    gid_t       st_gid;         /* 文件所属组id */
    time_t      st_mtime;       /* 文件最后修改时间 */
    time_t      st_atime;       /* 文件最后访问时间 */
    time_t      st_ctime;       /* 文件属性最后修改时间 */
    // ...
}

下面的例子 fileinfo.c 得到以上属性, 并显示出来

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>

void show_stat_info(char *, struct stat *);

    int
main( int argc, char **argv )
{
    struct stat info;
    if (argc > 1){
        if (stat(argv[1], &info) != -1){
            show_stat_info(argv[1], &info);
        }else{
            perror(argv[1]);
        }
    }
    return 0;
}

void show_stat_info(char *fname, struct stat *buf)
{
    printf("    mode:   %o\n", buf->st_mode);
    printf("   links:   %ld\n", buf->st_nlink);
    printf("    user:   %d\n", buf->st_uid);
    printf("   group:   %d\n", buf->st_gid);
    printf("    size:   %ld\n", buf->st_size);
    printf(" modtime:   %ld\n", buf->st_mtime);
    printf("    name:   %s\n", fname);
}

将模式字段转换成字符

st_mode 是一个 16 位的二进制数, 文件权限类型和权限被编码在这个数中 详情查看 Unix 文件权限

使用掩码来解码得到文件类型

文件类型在模式字段的第一个字节的前四位, 可以通过掩码来将其他部分置0, 从而 得到文件类型的值.

在 <sys/stat.h> 中有以下定义

#define     S_IFMT      0170000         /* type of file */
#define     S_IFREG     0100000         /* regular */
#define     S_IFDIR     0040000         /* directory */
#define     S_IFBLK     0060000         /* block special */
#define     S_IFCHR     0020000         /* character special */
#define     S_IFIFO     0010000         /* fifo */
#define     S_IFLNK     0120000         /* symbolic link */
#define     S_IFSOCK    0140000         /* socket */

S_IFMT 是一个掩码, 它的值是 0170000, 可以用来过滤出前四位表示的文件类型. S_IFREG 代表普通文件, 值是0100000, S_IFDIR 代表目录文件, 值是 0040000

if ((info.st_mode & 0170000) == 0040000)
   printf("this is a directory");
通过掩码将其他无关部分置0, 在于表示目录的代码比较, 从而判断这是否是一个目录.

更简单的方法是用 <sys/stat.h> 中的宏来替代上述代码:

#define S_ISFIFO(m) (((m) & (0170000)) == (0010000))
#define S_ISDIR(m) (((m) & (0170000)) == (0040000))
#define S_ISCHR(m) (((m) & (0170000)) == (0040000))
#define S_ISBLK(m) (((m) & (0170000)) == (0020000))
#define S_ISREG(m) (((m) & (0170000)) == (0100000))
使用宏的话可以这样写代码:
if (S_ISDIR(info.st_mode))
   printf("this is a directory");

解码得到许可权限

模式字段的最低9位是许可权限, 它标识了文件所有者, 组用户和其他用户的读写和执行 权限

void mode_to_letters(int mode, char str[])
{
    strcpy(str, "----------");
    if(S_ISDIR(mode)) str[0] = 'd';
    if(S_ISCHR(mode)) str[0] = 'c';
    if(S_ISBLK(mode)) str[0] = 'b';
    
    if (mode && S_IRUSR) str[1] = 'r';
    if (mode && S_IWUSR) str[2] = 'w';
    if (mode && S_IXUSR) str[3] = 'x';
    
    if (mode && S_IRGRP) str[4] = 'r';
    if (mode && S_IWGRP) str[5] = 'w';
    if (mode && S_IXGRP) str[6] = 'x';

    if (mode && S_IROTH) str[7] = 'r';
    if (mode && S_IWOTH) str[8] = 'w';
    if (mode && S_IXOTH) str[9] = 'x';
}

将用户/组 ID 转换成字符串

库函数 getpwuid 可以访问用户信息, getpwuid 需要 UID 作为参数, 返回一个指向 struct passwd 的指针, 这个接口定义在 /usr/include/pwd.h 中:

struct passwd {
    char * pw_name;     /* Username */
    char * pw_passwd;   /* Password */
    _uid_t pwd_uid;     /* User ID */
    _gid_t pw_gid;      /* Group ID */
    char * pw_gecos;    /* Real name */
    char * pw_dir;      /* Home directory */
    char * pw_shell;    /* Shell program */
};

getgrgid 函数可以返回 一个指向 struct group的指针, 这个接口定义在 /usr/include/grp.h 中:

struct group {
    char    *gr_name;       /* group name */
    char    *gr_passwd;     /* group password */
    gid_t   gr_gid;         /* group ID */
    char **gr_mem;          /* group members */
};

编写ls2.c

#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>

void do_ls(char []);
void dostat(char *);
void show_file_info(char *, struct stat *);
void mode_to_letters(int, char []);
char *uid_to_name(uid_t);
char *gid_to_name(gid_t);

    int
main( int argc, char **argv )
{
    if (argc == 1)
        do_ls(".");
    else
        while (--argc){
            printf("%s:\n", *++argv);
            do_ls(*argv);
        }
    return 0;
}

void do_ls(char dirname[])
{
    DIR *dir_ptr;
    struct dirent *direntp;

    if ((dir_ptr = opendir(dirname)) == NULL)
        fprintf(stderr, "ls2: cannot open %s\n", dirname);
    else
    {
        while((direntp = readdir(dir_ptr)))
            dostat(direntp->d_name);
        closedir(dir_ptr);
    }
}

void dostat(char *filename)
{
    struct stat info;
    if (stat(filename, &info) == -1)
        perror(filename);
    else
        show_file_info(filename, &info);
}


void show_file_info(char *filename, struct stat * info_p)
{
    char modestr[11];
    mode_to_letters(info_p->st_mode, modestr);
    printf("%s", modestr);
    printf("%4d", (int) info_p->st_nlink);
    printf("%-8s", uid_to_name(info_p->st_uid));
    printf("%-8s", gid_to_name(info_p->st_gid));
    printf("%8ld", (long)info_p->st_size);
    printf("%.12s", 4 + ctime(&info_p->st_mtime));
    printf("%s\n", filename);
}

void mode_to_letters(int mode, char str[])
{
    strcpy(str, "----------");

    if (S_ISDIR(mode)) str[0] = 'd';
    if (S_ISCHR(mode)) str[0] = 'c';
    if (S_ISBLK(mode)) str[0] = 'b';

    if (mode & S_IRUSR) str[1] = 'r';
    if (mode & S_IWUSR) str[2] = 'w';
    if (mode & S_IXUSR) str[3] = 'x';

    if (mode & S_IRGRP) str[4] = 'r';
    if (mode & S_IWGRP) str[5] = 'w';
    if (mode & S_IXGRP) str[6] = 'x';

    if (mode & S_IROTH) str[7] = 'r';
    if (mode & S_IWOTH) str[8] = 'w';
    if (mode & S_IXOTH) str[9] = 'x';
}

#include <pwd.h>

char *uid_to_name(uid_t uid)
{
    struct passwd *pw_ptr;
    static char numstr[10];
    if ((pw_ptr = getpwuid(uid)) == NULL){
        sprintf(numstr, "%d", uid);
        return numstr;
    }else
        return pw_ptr->pw_name;
}

#include <grp.h>
char *gid_to_name(gid_t gid)
{
    struct group *grp_ptr;
    static char numstr[10];

    if ((grp_ptr = getgrgid(gid)) == NULL){
        sprintf(numstr, "%d", gid);
        return numstr;
    }else
        return grp_ptr->gr_name;
}